# %pip install seaborn
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings("ignore")
from sklearn.preprocessing import StandardScaler, Normalizer, normalize
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
from sklearn import metrics
from sklearn.metrics import silhouette_score
from sklearn.cluster import AgglomerativeClustering
from scipy.cluster.hierarchy import dendrogram, linkage
from sklearn.metrics.pairwise import cosine_similarityCredit Card Dataset for Clustering
Δεδομένα dataset
Φόρτωση Δεδομένων
Το πρώτο βήμα είναι η συλλογή και η προετοιμασία των δεδομένων για ανάλυση. Στο unsupervised learning, τα δεδομένα δεν έχουν προκαθορισμένες ετικέτες, επομένως o καθαρισμός, η μετατροπή και η κανονικοποίηση των δεδομένων για την αφαίρεση τυχόν θορύβου ή ακραίων στοιχείων που μπορεί να επηρεάσουν την ανάλυση είναι αναγκαία.
# Συνάρτηση για plot
def plot_num_cat(feature, target, figsize=None):
fig = plt.figure(figsize=(8,4))
for value in df[target].unique():
sns.kdeplot(df[df[target]==value][feature])
fig.legend(labels=df[target].unique())
plt.title('{} distribution based on {}'.format(feature, target))
plt.show()# Φόρτωση των δεδομένων
df = pd.read_csv('C:\\Users\\Petros\\Desktop\\university\\4ο έτος\\8 semester\\εξόρυξη μεγάλου όγκου δεδομένων\\mining\\CC GENERAL.csv')
df.head()| CUST_ID | BALANCE | BALANCE_FREQUENCY | PURCHASES | ONEOFF_PURCHASES | INSTALLMENTS_PURCHASES | CASH_ADVANCE | PURCHASES_FREQUENCY | ONEOFF_PURCHASES_FREQUENCY | PURCHASES_INSTALLMENTS_FREQUENCY | CASH_ADVANCE_FREQUENCY | CASH_ADVANCE_TRX | PURCHASES_TRX | CREDIT_LIMIT | PAYMENTS | MINIMUM_PAYMENTS | PRC_FULL_PAYMENT | TENURE | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | C10001 | 40.900749 | 0.818182 | 95.40 | 0.00 | 95.4 | 0.000000 | 0.166667 | 0.000000 | 0.083333 | 0.000000 | 0 | 2 | 1000.0 | 201.802084 | 139.509787 | 0.000000 | 12 |
| 1 | C10002 | 3202.467416 | 0.909091 | 0.00 | 0.00 | 0.0 | 6442.945483 | 0.000000 | 0.000000 | 0.000000 | 0.250000 | 4 | 0 | 7000.0 | 4103.032597 | 1072.340217 | 0.222222 | 12 |
| 2 | C10003 | 2495.148862 | 1.000000 | 773.17 | 773.17 | 0.0 | 0.000000 | 1.000000 | 1.000000 | 0.000000 | 0.000000 | 0 | 12 | 7500.0 | 622.066742 | 627.284787 | 0.000000 | 12 |
| 3 | C10004 | 1666.670542 | 0.636364 | 1499.00 | 1499.00 | 0.0 | 205.788017 | 0.083333 | 0.083333 | 0.000000 | 0.083333 | 1 | 1 | 7500.0 | 0.000000 | NaN | 0.000000 | 12 |
| 4 | C10005 | 817.714335 | 1.000000 | 16.00 | 16.00 | 0.0 | 0.000000 | 0.083333 | 0.083333 | 0.000000 | 0.000000 | 0 | 1 | 1200.0 | 678.334763 | 244.791237 | 0.000000 | 12 |
Ακολουθεί το λεξικό δεδομένων για το σύνολο δεδομένων πιστωτικών καρτών:
- CUST_ID : Ταυτότητα κατόχου πιστωτικής κάρτας
- BALANCE : Ποσό υπολοίπου που απομένει στον λογαριασμό τους για να κάνουν αγορές
- BALANCE_FREQUENCY : Πόσο συχνά ενημερώνεται το Υπόλοιπο (1 = ενημερώνεται συχνά, 0 = δεν ενημερώνεται συχνά)
- PURCHASES : Ποσό αγορών που έγιναν από λογαριασμό
- ONEOFF_PURCHASES : Το μέγιστο ποσό αγοράς πραγματοποιείται με μία κίνηση
- INSTALLMENTS_PURCHASES : Ποσό αγοράς που έγινε με δόσεις
- CASH_ADVANCE: Μετρητά προκαταβολικά από τον χρήστη
- PURCHASES_FREQUENCY: Πόσο συχνά γίνονται οι αγορές (1 = αγοράζεται συχνά, 0 = δεν αγοράζεται συχνά)
- ONEOFFPURCHASESFREQUENCY: Πόσο συχνά γίνονται οι αγορές με μία κατάθεση (1 = αγοράζεται συχνά, 0 = δεν αγοράζεται συχνά)
- PURCHASESINSTALLMENTSFREQUENCY : Πόσο συχνά γίνονται οι αγορές σε δόσεις (1 = γίνονται συχνά, 0 = δεν γίνονται συχνά)
- PURCHASESINSTALLMENTSFREQUENCY : Πόσο συχνά καταβάλλονται τα μετρητά προκαταβολικά
- CASHADVANCETRX : Αριθμός συναλλαγών που πραγματοποιήθηκαν με “Cash in Advanced”
- PURCHASES_TRX : Αριθμός συναλλαγών αγοράς που πραγματοποιήθηκαν
- CREDIT_LIMIT : Όριο πιστωτικής κάρτας για τον χρήστη
- PAYMENTS: Ποσό πληρωμής που έγινε από τον χρήστη
- MINIMUM_PAYMENTS : Ελάχιστο ποσό πληρωμών που πραγματοποιούνται από τον χρήστη
- PRCFULLPAYMENT: Ποσοστό της πλήρους πληρωμής που καταβάλλεται από τον χρήστη
- TENURE : Περίοδος θητείας της υπηρεσίας πιστωτικής κάρτας για τον χρήστη
Έρευνα Δεδομένων
Σε αυτό το σημείο πρέπει να ελέγξουμε την ποιότητα των δεδομένων και να αξιολογήσουμε τυχόν προβλήματα στα δεδομένα όπως:
- αν υπάρχουν μηδενικές τιμές σε κάθε στήλη
- αν κάθε στήλη έχει τον κατάλληλο τύπο δεδομένων
- αν υπάρχουν ακραίες τιμές
- αν υπάρχουν διπλότυπες σειρές
- κατανομή για κάθε στήλη (skewness)
# Τύποι δεδομένων και non-null τιμές κάθε στήλης
df.info()<class 'pandas.core.frame.DataFrame'>
RangeIndex: 8950 entries, 0 to 8949
Data columns (total 18 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 CUST_ID 8950 non-null object
1 BALANCE 8950 non-null float64
2 BALANCE_FREQUENCY 8950 non-null float64
3 PURCHASES 8950 non-null float64
4 ONEOFF_PURCHASES 8950 non-null float64
5 INSTALLMENTS_PURCHASES 8950 non-null float64
6 CASH_ADVANCE 8950 non-null float64
7 PURCHASES_FREQUENCY 8950 non-null float64
8 ONEOFF_PURCHASES_FREQUENCY 8950 non-null float64
9 PURCHASES_INSTALLMENTS_FREQUENCY 8950 non-null float64
10 CASH_ADVANCE_FREQUENCY 8950 non-null float64
11 CASH_ADVANCE_TRX 8950 non-null int64
12 PURCHASES_TRX 8950 non-null int64
13 CREDIT_LIMIT 8949 non-null float64
14 PAYMENTS 8950 non-null float64
15 MINIMUM_PAYMENTS 8637 non-null float64
16 PRC_FULL_PAYMENT 8950 non-null float64
17 TENURE 8950 non-null int64
dtypes: float64(14), int64(3), object(1)
memory usage: 1.2+ MB
# Ύπαρξη διπλότυπων στηλών
df.duplicated().sum()0
Η έξοδος που προκύπτει από το παρακάτω απόσπασμα κώδικα είναι ένα πλέγμα ιστογραμμάτων, όπου κάθε γράφημα αντιπροσωπεύει την κατανομή ενός αριθμητικού χαρακτηριστικού στο DataFrame. Αυτή η οπτικοποίηση μας επιτρέπει να δούμε την κατανομή και το εύρος τιμών για κάθε αριθμητικό χαρακτηριστικό στο σύνολο δεδομένων.
# Ιστογράμματα χαρακτηριστικών
numerical_features=[feature for feature in df.columns if df[feature].dtypes!='object']
df[numerical_features].hist(bins=15, figsize=(15, 15), layout=(6, 3));
print(numerical_features)['BALANCE', 'BALANCE_FREQUENCY', 'PURCHASES', 'ONEOFF_PURCHASES', 'INSTALLMENTS_PURCHASES', 'CASH_ADVANCE', 'PURCHASES_FREQUENCY', 'ONEOFF_PURCHASES_FREQUENCY', 'PURCHASES_INSTALLMENTS_FREQUENCY', 'CASH_ADVANCE_FREQUENCY', 'CASH_ADVANCE_TRX', 'PURCHASES_TRX', 'CREDIT_LIMIT', 'PAYMENTS', 'MINIMUM_PAYMENTS', 'PRC_FULL_PAYMENT', 'TENURE']
- Οι περισσότερες στήλες έχουν τεράστιο αριθμό τιμών 0.
- Επιπλέον, οι περισσότερες στήλες είναι πολύ λοξές προς τα δεξιά, όπως φαίνεται από τα ιστογράμματα.
- Κατά την περαιτέρω διερεύνηση της στήλης BALANCE, φαίνεται ότι υπάρχουν πολλές πιστωτικές κάρτες με 0 υπόλοιπα.
- Υποστηρίζεται επίσης με τόσα 0 ποσά αγοράς στη στήλη PURCHASES.
- Οι περισσότεροι λογαριασμοί πιστωτικών καρτών έχουν 1 βαθμολογία στη στήλη BALANCE_FREQUENCY, κάτι που δείχνει ότι οι περισσότεροι πελάτες χρησιμοποιούν συχνά την πιστωτική κάρτα.
- Ωστόσο, αυτό διαφέρει από το ONEOFF_PURCHASES και το PURCHASES_INSTALLMENT_FREQUENCY, όπου η πλειοψηφία των πελατών δεν χρησιμοποιεί πιστωτικές κάρτες για εφάπαξ συναλλαγές ή πληρωμές σε δόσεις.
Στήλες χωρίς δεδομένα: με τον παρακάτω κώδικα μπορούμε να δούμε ποιές στήλες περιέχουν null τιμές και πόσες φορές εμφανίζονται αυτές.
# Στήλες που περιέχουν null τιμες
df.isna().sum().sort_values(ascending=False).head(5)MINIMUM_PAYMENTS 313
CREDIT_LIMIT 1
CUST_ID 0
BALANCE 0
PRC_FULL_PAYMENT 0
dtype: int64
Συμπεραίνουμε πως λείπουν τιμές στο CREDIT_LIMIT (1 τιμή λείπει) και στο MINIMUM_PAYMENTS (313 τιμές λείπουν).
Καθαρισμός Δεδομένων
Ο καθαρισμός δεδομένων περιλαμβάνει την αφαίρεση δεδομένων που λείπουν, καθώς και τη διόρθωση τυχόν σφαλμάτων ή ασυνεπειών στα δεδομένα.
# Αντίγραφο του dataset
df_copy=df.copy()Το CUST_ID θα αφαιρεθεί επειδή έχει μοναδικές τιμές.
df_copy = df_copy.drop(['CUST_ID'], axis=1)Ο παρακάτω κώδικας συμπληρώνει τις τιμές που λείπουν στις στήλες “CREDIT_LIMIT” και “MINIMUM_PAYMENTS” του DataFrame df_copy με τις μέσες τιμές των αντίστοιχων στηλών τους. Χρησιμοποιεί τη συνάρτηση fillna() για να αντικαταστήσει τις τιμές που λείπουν με τη μέση τιμή που υπολογίζεται χρησιμοποιώντας τη συνάρτηση mean(). Η παράμετρος inplace=True διασφαλίζει ότι οι αλλαγές γίνονται απευθείας στο DataFrame df_copy χωρίς να δημιουργηθεί νέο αντίγραφο.
df_copy["CREDIT_LIMIT"].fillna(df_copy["CREDIT_LIMIT"].mean(), inplace=True)
df_copy["MINIMUM_PAYMENTS"].fillna( df_copy["MINIMUM_PAYMENTS"].mean(), inplace=True)Heatmap των δεδομένων
plt.figure(figsize=(10, 10))
sns.heatmap(round(df[numerical_features].corr(method='spearman'), 2),
annot=True, mask=None, cmap='GnBu')
plt.show()Μετασχηματισμός δεδομένων
Ο μετασχηματισμός δεδομένων περιλαμβάνει τη μετατροπή των δεδομένων σε μια μορφή κατάλληλη για ανάλυση. Αυτό μπορεί να περιλαμβάνει την κλιμάκωση ή την κανονικοποίηση των δεδομένων. Εμείς θα εφαρμόσουμε κλιμάκωση.
# Κλιμάκωση των δεδομένων
scaler = StandardScaler()
scaled_df = scaler.fit_transform(df_copy) Ομαδοποίηση με KMeans
Οι αλγόριθμοι ομαδοποίησης ομαδοποιούν παρόμοια σημεία δεδομένων με βάση τα χαρακτηριστικά τους. Αυτό μπορεί να είναι χρήσιμο για τον εντοπισμό προτύπων ή δομής στα δεδομένα ή για την τμηματοποίηση των δεδομένων σε διαφορετικές ομάδες.
Silhouette Coefficient Method: Μια υψηλότερη βαθμολογία σχετίζεται με ένα μοντέλο με καλύτερα καθορισμένα cluster. Ο Silhouette Coefficient ορίζεται για κάθε δείγμα και αποτελείται από δύο βαθμολογίες:
- Η μέση απόσταση μεταξύ ενός δείγματος και όλων των άλλων σημείων της ίδιας κατηγορίας.
- Η μέση απόσταση μεταξύ ενός δείγματος και όλων των άλλων σημείων στο επόμενο κοντινότερο σύμπλεγμα.
Ένας υψηλότερος συντελεστής υποδηλώνει ότι το αντικείμενο ταιριάζει καλά με το δικό του cluster και πως δεν ταιριάζει με γειτονικά cluster.
The Elbow Method - inertia: Το Elbow Method είναι μια τεχνική για τον προσδιορισμό του βέλτιστου αριθμού cluster με βάση το σχήμα του διαγράμματος inertia. Στο πλαίσιο της ομαδοποίησης, η inertia (αδράνεια) είναι μια μέτρηση που χρησιμοποιείται για την αξιολόγηση της ποιότητας των αποτελεσμάτων της ομαδοποίησης. Μετρά το άθροισμα των τετραγώνων αποστάσεων μεταξύ κάθε σημείου δεδομένων και του κέντρου του cluster του. Ο στόχος των αλγορίθμων ομαδοποίησης όπως το K-means είναι η ελαχιστοποίηση της inertia.
inertia = []
silhouetteScore = []
# υπολογισμός silhouette score και inertia
for i in range(2, 12):
kmeans = KMeans(n_clusters=i, init='k-means++', random_state=0)
kmeans.fit(scaled_df)
inertia.append(kmeans.inertia_)
silhouetteScore.append(silhouette_score(scaled_df, kmeans.predict(scaled_df)))
# Plot
plt.figure(figsize=(10,5))
plt.subplot(1, 2, 1)
plt.plot(range(2, 12), inertia)
plt.title('The Elbow Method')
plt.xlabel('Number of clusters')
plt.ylabel('Inertia')
plt.subplot(1, 2, 2)
plt.plot(range(2, 12), silhouetteScore)
plt.title('Silhouette Score')
plt.xlabel('Number of clusters')
plt.ylabel('Silhouette Score')
plt.tight_layout()
plt.show()Θέλουμε να διαλέξουμε ένα σημείο όπου το inertia είναι χαμηλό και το silhouette score ψηλό. Σύμφωνα με τα παραπάνω γραφήματα, παρατηρούμε να ισχύουν ταυτόχρονα οι δύο προυποθέσεις στα 9 cluster.
kmeans = KMeans(n_clusters=9)
kmeans.fit(scaled_df)
# Αντιστοίχιση label σε cluster
cluster_labels = KMeans(n_clusters=9).fit_predict(scaled_df)Μείωση διαστάσεων με PCA
Οι αλγόριθμοι μείωσης διαστάσεων μειώνουν τον αριθμό των χαρακτηριστικών στα δεδομένα, ενώ διατηρούν όσο το δυνατόν περισσότερες πληροφορίες. Αυτό μπορεί να είναι χρήσιμο για την οπτικοποίηση δεδομένων υψηλών διαστάσεων ή για τη μείωση της πολυπλοκότητας των δεδομένων για περαιτέρω ανάλυση.
Το παρακάτω απόσπασμα κώδικα εκτελεί PCA στο scaled_df DataFrame και δημιουργεί ένα νέο DataFrame X_PCA με τα κύρια χαρακτηριστικά που εξάγονται από τα δεδομένα.
dist = 1-cosine_similarity(scaled_df)
# Εφαρμόζουμε PCA για να οπτικοποιήσουμε στις 2 διαστάσεις
pca = PCA(2)
pca.fit(dist)
X_PCA = pca.transform(dist)
# Οπτικοποίηση των clusters
plt.figure(figsize=(10,8))
sns.scatterplot(x=X_PCA[:, 0], y=X_PCA[:, 1],
hue=cluster_labels, palette=sns.color_palette('hls', kmeans.cluster_centers_.shape[0]), s=50)
plt.title('Cluster of Customers', size=15, pad=10)
plt.legend(loc=0, bbox_to_anchor=[1,1])
plt.show()Η συνάρτηση cosine_similarity υπολογίζει την ομοιότητα συνημιτόνου ανά ζεύγη μεταξύ των σειρών του πίνακα scaled_df. Εφόσον το PCA λειτουργεί με αποστάσεις, η αφαίρεση της ομοιότητας συνημιτόνου από το 1 το μετατρέπει σε μέτρο απόστασης. Αυτό το βήμα είναι απαραίτητο επειδή το PCA στοχεύει να βρει τις κατευθύνσεις της μέγιστης διακύμανσης στα δεδομένα και τα μέτρα απόστασης είναι καλύτερα κατάλληλα για αυτόν τον σκοπό.
Ανάλυση των clusters
Για λόγους ανάλυσης, θα επιλέξουμε μόνο τα παρακάτω αριθμητικά χαρακτηριστικά για οπτικοποίηση των cluster.
numerical_features2 = ['BALANCE', 'PURCHASES', 'CREDIT_LIMIT', 'PAYMENTS', 'TENURE']# Οπτικοποίηση των cluster σε κάθε μεταβλητή
df['cluster'] = cluster_labels
for feature in numerical_features2:
plot_num_cat(feature, 'cluster')df_out = df.groupby(by='cluster').sum()[['BALANCE', 'PURCHASES', 'CREDIT_LIMIT', 'PAYMENTS', 'TENURE']].reset_index()
df_out.head(3)
sns.set_style("darkgrid")
plt.figure(figsize=(22, 4))
plt.subplot(1, 5, 1)
sns.barplot(x='cluster', y='BALANCE', data=df_out, palette='crest', seed=123)
plt.subplot(1, 5, 2)
sns.barplot(x='cluster', y='PURCHASES', data=df_out, palette='crest', seed=123)
plt.subplot(1, 5, 3)
sns.barplot(x='cluster', y='CREDIT_LIMIT', data=df_out, palette='crest', seed=123)
plt.subplot(1, 5, 4)
sns.barplot(x='cluster', y='PAYMENTS', data=df_out, palette='crest', seed=123)
plt.subplot(1, 5, 5)
sns.barplot(x='cluster', y='TENURE', data=df_out, palette='crest', seed=123)<Axes: xlabel='cluster', ylabel='TENURE'>
Από τα παραπάνω γραφήματα, μπορούμε να λάβουμε πληροφορίες σχετικά με τις πιστωτικές κάρτες που ανήκουν σε άτομα κάθε cluster. Για παράδειγμα, τα άτομα από το cluster 1 έχουν υψηλό ποσό υπολοίπου στον λογαριασμό τους, όπως και όριο κάρτας και περίοδο ισχύος της κάρτας ενώ έχουν μεσαίο ποσό αγορών από χρήστη και χαμηλό ποσο αγορών από λογαριασμό.
Ιεραχική ομαδοποίηση
Η ιεραρχική ομαδοποίηση είναι μια τεχνική ομαδοποίησης που oμαδοποιεί παρόμοια σημεία δεδομένων σε clusters με βάση την κοντινότητά τους. Σε αντίθεση με άλλους αλγόριθμους ομαδοποίησης που απαιτούν τον καθορισμό του αριθμού των cluster εκ των προτέρων, η ιεραρχική ομαδοποίηση δημιουργεί μια ιεραρχία cluster που μπορεί να αναπαρασταθεί ως δενδρόγραμμα.
Agglomerative: Γνωστή και ως bottom-up clustering ξεκινά με μεμονωμένα σημεία δεδομένων ως ξεχωριστά clusters και σταδιακά τα συγχωνεύει με βάση ένα μέτρο ομοιότητας έως ότου ληφθεί ένας καθορισμένος αριθμός cluster, στη περίπτωσή μας 9.
# Δενδρόγραμμα
dendro = dendrogram(linkage(scaled_df, method='ward'))
plt.plot([250]*20000, color='r')
plt.plot([150]*20000, color='r')
plt.show()
# Bottom-up Ιεραρχική ομαδοποίηση
model = AgglomerativeClustering(n_clusters=9, affinity='euclidean', linkage='ward')# Αντιστοίχιση label σε cluster
hierarchical = model.fit_predict(scaled_df)
hierarchical_labels = hierarchicalΑκολουθεί η οπτικοποίηση της ιεραρχικής ομαδοποίησης.
dist = 1-cosine_similarity(scaled_df)
# Μείωση διαστάσεων στις 2D
pca = PCA(2)
pca.fit(dist)
X_PCA = pca.transform(dist)
# Οπτικοποίηση των clusters
plt.figure(figsize=(10,8))
sns.scatterplot(x=X_PCA[:, 0], y=X_PCA[:, 1],
hue=hierarchical, palette=sns.color_palette('hls', len(np.unique(hierarchical))), s=50)
plt.title('Cluster of Customers', size=15, pad=10)
plt.legend(loc=0, bbox_to_anchor=[1,1])
plt.show()Ανάλυση clusters
# Οπτικοποίηση των cluster σε κάθε μεταβλητή
df['cluster'] = hierarchical_labels
for feature in numerical_features2:
plot_num_cat(feature, 'cluster')df_out = df.groupby(by='cluster').sum()[['BALANCE', 'PURCHASES', 'CREDIT_LIMIT', 'PAYMENTS', 'TENURE']].reset_index()
df_out.head(3)
sns.set_style("darkgrid")
plt.figure(figsize=(22, 4))
plt.subplot(1, 5, 1)
sns.barplot(x='cluster', y='BALANCE', data=df_out, palette='crest', seed=123)
plt.subplot(1, 5, 2)
sns.barplot(x='cluster', y='PURCHASES', data=df_out, palette='crest', seed=123)
plt.subplot(1, 5, 3)
sns.barplot(x='cluster', y='CREDIT_LIMIT', data=df_out, palette='crest', seed=123)
plt.subplot(1, 5, 4)
sns.barplot(x='cluster', y='PAYMENTS', data=df_out, palette='crest', seed=123)
plt.subplot(1, 5, 5)
sns.barplot(x='cluster', y='TENURE', data=df_out, palette='crest', seed=123)<Axes: xlabel='cluster', ylabel='TENURE'>
Παρόμοιες πληροφορίες με εκείνες που αναφέρθηκαν στο k-Means μπορούν να παρατηρηθούν και να εξαχθούν από τα παραπάνω γράφηματα.